/*******************************************************************************
 * Copyright (c) 2001-2003 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.syncinfo;

import java.io.Externalizable;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;

import net.sourceforge.perforce.core.internal.Policy;

/**
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class ResourceSyncInfo implements Externalizable {
  //----------------------------------------------------------------------------
  // Public Constants

  /** Indicating non existing resource sync info. */
  public static final ResourceSyncInfo NULL_INFO = new ResourceSyncInfo(true);
  /** Constant for the add action. */
  public static final String ACTION_ADD = "add"; //$NON-NLS-1$
  /** Constant for the branch action. */
  public static final String ACTION_BRANCH = "branch"; //$NON-NLS-1$
  /** Constant for the delete action. */
  public static final String ACTION_DELETE = "delete"; //$NON-NLS-1$
  /** Constant of the edit action. */
  public static final String ACTION_EDIT = "edit"; //$NON-NLS-1$

  //----------------------------------------------------------------------------
  // ------- Constants

  /** serial number of the class. */
  static final long serialVersionUID = -5903762684861295448L;

  // ---------------------------------------------------------------------------
  // Private Constants

  /** Constant: Resource is resolved */
  private static final int FLAG_RESOLVED = 0x00000001;
  /** Constant: Resource is opened by someone else. */
  private static final int FLAG_OTHER_OPEN = 0x00000002;
  /** Constants: Resource is locked by us. */
  private static final int FLAG_OUR_LOCK = 0x00000004;
  /** Constant: Have revision is added. */
  private static final int FLAG_HAVE_ADD = 0x00000010;
  /** Constant: Have revision is branched. */
  private static final int FLAG_HAVE_BRANCH = 0x00000020;
  /** Constant: Have revision is deleted. */
  private static final int FLAG_HAVE_DELETE = 0x00000040;
  /** Constant: Have revision is opened for edit. */
  private static final int FLAG_HAVE_EDIT = 0x00000080;
  /** Constant: Head revision was added. */
  private static final int FLAG_HEAD_ADD = 0x00000100;
  /** Constant: Head revision was branched. */
  private static final int FLAG_HEAD_BRANCH = 0x00000200;
  /** Constant: Head revision was deleted. */
  private static final int FLAG_HEAD_DELETE = 0x00000400;
  /** Constant: Head revision was edited. */
  private static final int FLAG_HEAD_EDIT = 0x00000800;
  /** Constant: Marks the NULL sync info */
  private static final int FLAG_NULL_INFO = 0x10000000;

  /** Constant: Bit mask including all 'have' states. */
  private static final int FLAG_HAVE_ALL = 0x000000f0;
  /** Constant: Bit mask including all 'head' states. */
  private static final int FLAG_HEAD_ALL = 0x00000f00;

  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores a creation timestamp. */
  private long timestamp = System.currentTimeMillis();
  /** Stores the client filename. */
  private String clientFileName = ""; //$NON-NLS-1$
  /** Stores the depot filename. */
  private String depotFileName = ""; //$NON-NLS-1$
  /** Stores the head type. */
  private String headType = ""; //$NON-NLS-1$
  /** Stores the head time. */
  private Date headTime = new Date();
  /** Stores the head revision number. */
  private int headRev = 0;
  /** Stores the head changelist number. */
  private int headChange = 0;
  /** Stores the client changelist number. */
  private int change;
  /** Stores the have revision. */
  private int haveRev = 0;
  /** Indicates whether the file is resolved. */
  private int flags = FLAG_RESOLVED;

  // -------------------------------------------------------------------------
  // Public Constructors

  /** Constructor for ResourceSyncInfo. */
  public ResourceSyncInfo() {
  }

  // ------------------------------------------------------------------------
  // Private Constructors

  /**
   * Constructor for ResourceSyncInfo.
   * 
   * @param isNull indicates whether we want to create a null sync info.
   */
  private ResourceSyncInfo(boolean isNull) {
    if (isNull) {
      flags |= FLAG_NULL_INFO;
    }
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  /**
   * Returns the entry's timestamp.
   * 
   * @return the timestamp.
   */
  public long getTimestamp() {
    return timestamp;
  }

  /**
   * Gets the action.
   * 
   * @return Returns a String
   */
  public String getAction() {
    switch (flags & FLAG_HAVE_ALL) {
      case 0:
        return "";

      case FLAG_HAVE_ADD:
        return ACTION_ADD;

      case FLAG_HAVE_BRANCH:
        return ACTION_BRANCH;

      case FLAG_HAVE_DELETE:
        return ACTION_DELETE;

      case FLAG_HAVE_EDIT:
        return ACTION_EDIT;

      default:
        throw new IllegalStateException();
    }
  }

  /**
   * Sets the action.
   * 
   * @param action The action to set
   */
  public void setAction(String action) {
    Policy.assertNotNull(action);

    if (action.equals(ACTION_ADD)) {
      flags &= ~FLAG_HAVE_ALL;
      flags |= FLAG_HAVE_ADD;
    } else if (action.equals(ACTION_BRANCH)) {
      flags &= ~FLAG_HAVE_ALL;
      flags |= FLAG_HAVE_BRANCH;
    } else if (action.equals(ACTION_DELETE)) {
      flags &= ~FLAG_HAVE_ALL;
      flags |= FLAG_HAVE_DELETE;
    } else if (action.equals(ACTION_EDIT)) {
      flags &= ~FLAG_HAVE_ALL;
      flags |= FLAG_HAVE_EDIT;
    } else {
      flags &= ~FLAG_HAVE_ALL;
    }
  }

  /**
   * Gets the depotFileName.
   * 
   * @return Returns a String
   */
  public String getClientFileName() {
    return clientFileName;
  }

  /**
   * Sets the clientFileName.
   * 
   * @param clientFileName The clientFileName to set
   */
  public void setClientFileName(String clientFileName) {
    Policy.assertNotNull(clientFileName);
    this.clientFileName = clientFileName;
  }

  /**
   * Gets the depotFileName.
   * 
   * @return Returns a String
   */
  public String getDepotFileName() {
    return depotFileName;
  }

  /**
   * Sets the depotFileName.
   * 
   * @param depotFileName The depotFileName to set
   */
  public void setDepotFileName(String depotFileName) {
    Policy.assertNotNull(depotFileName);
    this.depotFileName = depotFileName;
  }

  /**
   * Gets the headAction.
   * 
   * @return the head action.
   */
  public String getHeadAction() {
    switch (flags & FLAG_HEAD_ALL) {
      case 0:
        return "";

      case FLAG_HEAD_ADD:
        return ACTION_ADD;

      case FLAG_HEAD_BRANCH:
        return ACTION_BRANCH;

      case FLAG_HEAD_DELETE:
        return ACTION_DELETE;

      case FLAG_HEAD_EDIT:
        return ACTION_EDIT;

      default:
        throw new IllegalStateException();
    }
  }

  /**
   * Sets the headAction.
   * 
   * @param action The headAction to set
   */
  public void setHeadAction(String action) {
    Policy.assertNotNull(action);

    if (action.equals(ACTION_ADD)) {
      flags &= ~FLAG_HEAD_ALL;
      flags |= FLAG_HEAD_ADD;
    } else if (action.equals(ACTION_BRANCH)) {
      flags &= ~FLAG_HEAD_ALL;
      flags |= FLAG_HEAD_BRANCH;
    } else if (action.equals(ACTION_DELETE)) {
      flags &= ~FLAG_HEAD_ALL;
      flags |= FLAG_HEAD_DELETE;
    } else if (action.equals(ACTION_EDIT)) {
      flags &= ~FLAG_HEAD_ALL;
      flags |= FLAG_HEAD_EDIT;
    } else {
      flags &= ~FLAG_HEAD_ALL;
    }
  }

  /**
   * Gets the headType.
   * 
   * @return Returns a String
   */
  public String getHeadType() {
    return headType;
  }

  /**
   * Sets the headType.
   * 
   * @param headType The headType to set
   */
  public void setHeadType(String headType) {
    Policy.assertNotNull(headType);
    this.headType = headType;
  }

  /**
   * Gets the headRev.
   * 
   * @return Returns a Integer
   */
  public int getHeadRev() {
    return headRev;
  }

  /**
   * Sets the headRev.
   * 
   * @param headRev The headRev to set
   */
  public void setHeadRev(int headRev) {
    this.headRev = headRev;
  }

  /**
   * Gets the headChange.
   * 
   * @return Returns a Integer
   */
  public int getHeadChange() {
    return headChange;
  }

  /**
   * Sets the headChange.
   * 
   * @param headChange The headChange to set
   */
  public void setHeadChange(int headChange) {
    this.headChange = headChange;
  }

  /**
   * Gets the change.
   * 
   * @return return an Integer.
   */
  public int getChange() {
    return change;
  }

  /**
   * Sets the change.
   * 
   * @param change the new change value.
   */
  public void setChange(int change) {
    this.change = change;
  }

  /**
   * Gets the haveRev.
   * 
   * @return Returns a Integer
   */
  public int getHaveRev() {
    return haveRev;
  }

  /**
   * Sets the haveRev.
   * 
   * @param haveRev The haveRev to set
   */
  public void setHaveRev(int haveRev) {
    this.haveRev = haveRev;
  }

  /**
   * Gets the head time.
   * 
   * @return the head time.
   */
  public Date getHeadTime() {
    return headTime;
  }

  /**
   * Sets the head time.
   * 
   * @param headTime the new head time.
   */
  public void setHeadTime(Date headTime) {
    Policy.assertNotNull(headTime);
    this.headTime = headTime;
  }

  /**
   * Checks whether the file is resolved.
   * 
   * @return true if resolved.
   */
  public boolean isResolved() {
    return (flags & FLAG_RESOLVED) > 0;
  }

  /**
   * Sets whether the file is resolved.
   * 
   * @param resolved the new resolved value.
   */
  public void setResolved(boolean resolved) {
    if (resolved) {
      this.flags |= FLAG_RESOLVED;
    } else {
      this.flags &= ~FLAG_RESOLVED;
    }
  }

  /**
   * Checks whether the file is opened by another one.
   * 
   * @return true or false.
   */
  public boolean isOtherOpen() {
    return (flags & FLAG_OTHER_OPEN) > 0;
  }

  /**
   * Sets the otherOpen value.
   * 
   * @param otherOpen true or false.
   */
  public void setOtherOpen(boolean otherOpen) {
    if (otherOpen) {
      this.flags |= FLAG_OTHER_OPEN;
    } else {
      this.flags &= ~FLAG_OTHER_OPEN;
    }
  }

  /**
   * Returns if the resource is locked by us or not.
   * 
   * @return true or false.
   */
  public boolean hasOurLock() {
    return (flags & FLAG_OUR_LOCK) > 0;
  }

  /**
   * Changes the value that indicates whether the resource is locked locally or
   * not.
   * 
   * @param ourLock new value
   */
  public void setHasOurLock(boolean ourLock) {
    if (ourLock) {
      this.flags |= FLAG_OUR_LOCK;
    } else {
      this.flags &= ~FLAG_OUR_LOCK;
    }
  }

  /**
   * Checks whether this is a NULL resource sync info.
   * 
   * @return true if this is a null info.
   */
  public boolean isNull() {
    return (flags & FLAG_NULL_INFO) > 0;
  }

  /**
   * Returns the name of the resource.
   * 
   * @return the resource's name.
   */
  public String getName() {
    String name = getClientFileName();
    return name.substring(name.lastIndexOf(File.separator) + 1);
  }

  public boolean isAdded() {
    return (flags & FLAG_HAVE_ADD) > 0;
  }

  public boolean isBranched() {
    return (flags & FLAG_HAVE_BRANCH) > 0;
  }

  /**
   * Checks whether the resource is deleted.
   * 
   * @return true if resource is deleted.
   */
  public boolean isDeleted() {
    if ((flags & FLAG_HAVE_ADD) > 0 || (flags & FLAG_HAVE_BRANCH) > 0) {
      return false;
    }

    return haveRev == 0
        || ((headRev == haveRev) && (flags & FLAG_HEAD_DELETE) > 0 && (flags & FLAG_HAVE_ALL) == 0);
  }

  public boolean isEdited() {
    return (flags & FLAG_HAVE_EDIT) > 0;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }


    if (!(other instanceof ResourceSyncInfo)) {
      return false;
    }

    ResourceSyncInfo otherSyncInfo = (ResourceSyncInfo) other;
    if ((headType.equals(otherSyncInfo.headType)) && (headTime.equals(otherSyncInfo.headTime))
        && (headRev == otherSyncInfo.headRev) && (headChange == otherSyncInfo.headChange)
        && (change == otherSyncInfo.change) && (haveRev == otherSyncInfo.haveRev)
        && (flags == otherSyncInfo.flags) && (clientFileName.equals(otherSyncInfo.clientFileName))
        && (depotFileName.equals(otherSyncInfo.depotFileName))) {
      return true;
    }
    return false;
  }

  // ------------------------------------------------------------- Public
  // Methods (Externalizable)

  public void readExternal(ObjectInput in) throws IOException {
    timestamp = in.readLong();
    clientFileName = in.readUTF();
    depotFileName = in.readUTF();
    headType = in.readUTF();
    headTime = new Date(in.readLong());
    headRev = in.readInt();
    headChange = in.readInt();

    change = in.readInt();
    haveRev = in.readInt();
    flags = in.readInt();
  }

  public void writeExternal(ObjectOutput out) throws IOException {
    out.writeLong(timestamp);
    out.writeUTF(clientFileName);
    out.writeUTF(depotFileName);
    out.writeUTF(headType);
    out.writeLong(headTime.getTime());
    out.writeInt(headRev);
    out.writeInt(headChange);
    out.writeInt(change);
    out.writeInt(haveRev);
    out.writeInt(flags);
  }
}
